﻿#if ENABLE_SharpZipLib
#if ENABLE_AutoItX


namespace suit_calcchain
{
    using System;
    using System.Xml;
    using System.Collections.Generic;
    using ICSharpCode.SharpZipLib.Zip;

    public class CalcChain : suit_base.SuiteBase
    {
        private string m_samplePath;
        private KDumpFile m_dumpFile;
        private autoIt.CAutoIt m_wndKill;
        private List<string> m_collTmpFiles = new List<string>();

        public override string GetCommand()
        {
            return ET_Improve.Global.CMD_CALC_CHAIN;
        }

        public override void Call()
        {
            try
            {
                LoadConfig();
                Dictionary<string, List<tool.CAddInfoHlp.CFileInfo>> fileDic =
                    tool.CAddInfoHlp.GetFolderFiles(m_samplePath,
                    ET_Improve.Global.CMD_CALC_CHAIN, ".xls|.et|.xlsx|.xlsb");

                //excelOp.ExcelOP.RegisterExcel(@"D:\Program Files\Microsoft Office\Office14");
                m_wndKill = new autoIt.CAutoIt();
                m_wndKill.Start();
                foreach (string sDir in fileDic.Keys)
                {
                    tool.LogTool.GetInstance().BeginUseLog(sDir, ET_Improve.Global.s_curCmdName);
                    foreach (tool.CAddInfoHlp.CFileInfo fi in fileDic[sDir])
                    {
                        tool.Output.DumpWriteLine(">>>:" + fi.FileName);
                        ProcessFile(fi);
                    }

                    tool.LogTool.GetInstance().EndUseLog();
                }
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                m_wndKill.Stop();
            }
        }

        protected void LoadConfig()
        {
            const string LOG_ROOT = "calc-chain";
            const string LOG_SAMPLE_PATH = "sample-path";
            const string LOG_OUTPUT_FILE = "output-file";
            try
            {
                foreach (XmlNode subNode in tool.ConfigReader.GetElementList(LOG_ROOT))
                {
                    switch (subNode.Name)
                    {
                        case LOG_SAMPLE_PATH:
                            m_samplePath = subNode.InnerText;
                            break;
                        case LOG_OUTPUT_FILE:
                            m_dumpFile = new KDumpFile(subNode.InnerText);
                            break;
                    }
                }
            }
            catch (System.Exception ex)
            {
                throw ex;
            }
        }

        protected void ProcessFile(tool.CAddInfoHlp.CFileInfo fi)
        {
            try
            {
                string sFileName = fi.FileName;
                string sExt = System.IO.Path.GetExtension(sFileName);
                if (sExt != ".xlsb")
                {
                    sFileName = GetSaveAsFile(sFileName, fi.Password);
                }
                if (sFileName == null)
                {
                    Console.WriteLine("error:" + sFileName);
                    return;
                }

                DumpCalcChain(sFileName);
                Clean();
            }
            catch (System.Exception ex)
            {
                tool.LogTool.GetInstance().AddLog(tool.LogTool.LOG_TYPE.ERROR, fi.FileName, "ProcessFile", ex);
            }
        }

        void Clean()
        {
            try
            {
                for (int i = 0; i < m_collTmpFiles.Count; ++i)
                {
                    string s = m_collTmpFiles[i];
                    if (System.IO.File.Exists(s))
                    {
                        System.IO.File.Delete(s);
                        m_collTmpFiles.RemoveAt(i);
                    }
                    else
                    {
                        m_collTmpFiles.RemoveAt(i);
                    }
                }
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        protected string GetSaveAsFile(string sFileName, string sPasswd)
        {
            try
            {
                excelOp.ExcelOP pExcel = new excelOp.ExcelOP();
                pExcel.Init(m_wndKill, true);
                pExcel.Open(sFileName, sPasswd);

                string sTmp = System.IO.Path.GetDirectoryName(sFileName);
                sTmp += "\\" + System.IO.Path.GetFileNameWithoutExtension(sFileName);
                sTmp += ".xlsb";
                sTmp = tool.CTmpFileHlp.GetSystemTmpName(sTmp);
                pExcel.SaveAs(sTmp);
                pExcel.Close();
                excelOp.ExcelOP.DestoryExcelOp(ref pExcel);
                pExcel = null;
                m_collTmpFiles.Add(sTmp);
                return sTmp;
            }
            catch (tool.ETImpException ex)
            {
                tool.LogTool.GetInstance().AddLog(tool.LogTool.LOG_TYPE.ERROR, sFileName, "GetSaveAsFile", ex);
            }
            catch (System.Exception ex)
            {
                tool.LogTool.GetInstance().AddLog(tool.LogTool.LOG_TYPE.ERROR, sFileName, "GetSaveAsFile", ex);
            }
            return null;
        }

        // 导出文件的calcChain
        protected void DumpCalcChain(string sFileName)
        {
            KCCHlp hlp = new KCCHlp();
            hlp.Begin();
            hlp.Prase(sFileName);
            m_dumpFile.Dump(sFileName, hlp.GetData());
            hlp.End();
        }
    }

    internal class KCCHlp
    {
        Dictionary<uint, string> m_sheetName;
        protected List<KCalcChain> m_data;
        public void Begin()
        {
            m_sheetName = new Dictionary<uint, string>();
            m_data = new List<KCalcChain>();
        }

        public void End()
        {

        }

        public List<KCalcChain> GetData()
        {
            return m_data;
        }

        public void Prase(string sFileName)
        {
            const string CALCCHAIN_BIN = "xl/calcChain.bin";
            const string WORKBOOK_BIN = "xl/workbook.bin";

            try
            {
                ZipEntry entry;
                ZipInputStream zis = new ZipInputStream(System.IO.File.OpenRead(sFileName));
                while ((entry = zis.GetNextEntry()) != null)
                {
                    if (entry.Name != CALCCHAIN_BIN && entry.Name != WORKBOOK_BIN)
                    {
                        continue;
                    }
                    int nSize = (int)entry.Size;
                    byte[] buffer = new byte[nSize];
                    int nReaded = zis.Read(buffer, 0, nSize);
                    if (nReaded < 0)
                    {
                        continue;
                    }
                    if (entry.Name == CALCCHAIN_BIN)
                    {
                        DecodeCalcChain(buffer);
                    }
                    else if (entry.Name == WORKBOOK_BIN)
                    {
                        DecodeSheetName(buffer);
                    }
                }
            }
            catch (System.Exception ex)
            {
                throw new tool.ETImpException(tool.LogTool.LOG_TYPE.ERROR, sFileName, "Error:Prase file", ex);
            }
        }

        protected void DecodeSheetName(byte[] data)
        {
            const int BRTBUNDLESH_ID = 156;
            xlsbHlp.KRecordEnum enmRec = new xlsbHlp.KRecordEnum();
            enmRec.Init(data);
            int nRecID = 0;
            int nRecSize = 0;
            byte[] recDat = null;
            while (enmRec.NextRec(ref recDat, ref nRecID, ref nRecSize))
            {
                if (nRecID == BRTBUNDLESH_ID && recDat != null)
                {
                    AddSheetName(recDat, nRecSize);
                }
            }
        }

        protected void AddSheetName(byte[] data, int nSize)
        {
            int iPos = 0;
            uint hsState = System.BitConverter.ToUInt32(data, iPos);
            iPos += 4;
            uint itabid = System.BitConverter.ToUInt32(data, iPos);
            iPos += 4;
            int nRelIdSz = System.BitConverter.ToInt32(data, iPos);
            iPos += 4;
            iPos += (nRelIdSz * 2);
            int nNameSz = System.BitConverter.ToInt32(data, iPos);
            iPos += 4;
            nNameSz *= 2;
            byte[] bySheetName = new byte[nNameSz];
            System.Array.Copy(data, iPos, bySheetName, 0, nNameSz);
            string sName = System.Text.Encoding.Unicode.GetString(bySheetName);
            iPos += nNameSz;
            if (nSize != iPos)
            {
                throw new Exception("Failed of parse BRTBUNDLESH!");
            }
            m_sheetName.Add(itabid, sName);
            System.Diagnostics.Debug.WriteLine(string.Format("id={0:D}  name={1}", itabid, sName));
        }

        protected void DecodeCalcChain(byte[] data)
        {
            const int BRTCALCCELL_ID = 63;
            xlsbHlp.KRecordEnum enmRec = new xlsbHlp.KRecordEnum();
            enmRec.Init(data);
            int nRecID = 0;
            int nRecSize = 0;
            byte[] recDat = null;
            while (enmRec.NextRec(ref recDat, ref nRecID, ref nRecSize))
            {
                if (nRecID == BRTCALCCELL_ID)
                {
                    AddCalcChain(recDat, nRecSize);
                }
            }
        }

        protected void AddCalcChain(byte[] data, int nSize)
        {
            int iPos = 0;
            int nSheet = System.BitConverter.ToInt32(data, iPos);
            iPos += 4;
            int nRow = System.BitConverter.ToInt32(data, iPos);
            iPos += 4;
            int nCol = System.BitConverter.ToInt32(data, iPos);
            iPos += 4;
            byte bGrbitl = data[iPos++];
            if (nSize != iPos)
            {
                throw new Exception("Failed of parse calcChain!");
            }
            KCalcChain cc = new KCalcChain();
            cc.ReferTo = CellToStr(nSheet, nRow, nCol);
            cc.bGrbitl = bGrbitl;
            m_data.Add(cc);
            
            System.Diagnostics.Debug.WriteLine(string.Format("sheet:{0} row:{1} col:{2} grbitl:{3:X2}",
                nSheet, nRow, nCol, bGrbitl));
        }

        protected string CellToStr(int nSheet, int nRow, int nCol)
        {
            if(nSheet < 0)
            {
                return null;
            }
            string sRet;
            uint uIdx = (uint)(nSheet + 1);
            if(m_sheetName.ContainsKey(uIdx))
            {
                sRet = m_sheetName[uIdx];
            }
            else
            {
                sRet = "!UNKNOWN!";
            }

            sRet += "!";
            sRet += ColumnToStr(nCol);
            sRet += string.Format("{0}", nRow + 1);
            return sRet;
        }

        protected string ColumnToStr(int nCol)
        {
            const int MAX_COLS_COUNT = 256;
            nCol = (nCol + MAX_COLS_COUNT) % MAX_COLS_COUNT;
            string sCol = System.Convert.ToChar(65 + nCol % 26).ToString();
            nCol /= 26;
            while(nCol != 0)
            {
                sCol += System.Convert.ToChar(65 + nCol % 26 - 1).ToString();
                nCol /= 26;
            }
            return sCol;
        }
    }

    // 如果超过一定大小就写到其他文件中去
    internal class KDumpFile
    {
        private int m_nPart;
        private string m_sOutput;
        private System.IO.StreamWriter m_sw;
        private const int ONE_MB = 1048576;
        public KDumpFile(string _output)
        {
            m_sOutput = _output;
        }

        public void Dump(string sFileName, List<KCalcChain> dat)
        {
            // 都是估算的: 一般20MB左右一个文件，假设一行数据20字节
            int nDatSize = 20 * dat.Count;
            Begin(nDatSize);
            // 添加数据
            m_sw.WriteLine(string.Format("[{0}]", sFileName));
            foreach (KCalcChain cc in dat)
            {
                m_sw.WriteLine(cc.Formated());
            }
            End();
        }

        protected void Begin(int nAddSz)
        {
            m_nPart = 0;
            string sFileName = m_sOutput;
            if(System.IO.File.Exists(m_sOutput))
            {
                System.IO.FileInfo fi = new System.IO.FileInfo(m_sOutput);
                if ((fi.Length + nAddSz) > (20 * ONE_MB))
                {
                    // 创建一个新的文件
                    ++m_nPart;
                    sFileName += string.Format("({0})", m_nPart);
                }
            }
            m_sw = System.IO.File.AppendText(sFileName);
            m_sw.AutoFlush = true;
        }

        protected void End()
        {
            m_sw.Close();
        }
    }

    class KCalcChain
    {
        private const byte MASK_CHILDFMLA = 0x01;
        private const byte MASK_NEWDEPLEVEL = 0x02;
        private const byte MASK_NEWTHREAD = 0x04;
        private const byte MASK_ARRAYFMLA = 0x08;
        public string ReferTo { set; get; }
        public byte bGrbitl { set; get; }
        public string Formated()
        {
            return string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t", ReferTo,
                GetNewDepLevel(), GetChildChain(), GetNewThread(), GetArrayFmla());
        }

        public int GetNewDepLevel()
        {
            if(MASK_NEWDEPLEVEL == (bGrbitl & MASK_NEWDEPLEVEL))
            {
                return 1;
            }
            return 0;
        }

        public int GetChildChain()
        {
            if(MASK_CHILDFMLA == (bGrbitl & MASK_CHILDFMLA))
            {
                return 1;
            }
            return 0;
        }

        public int GetNewThread()
        {
            if(MASK_NEWTHREAD == (bGrbitl & MASK_NEWTHREAD))
            {
                return 1;
            }
            return 0;
        }

        public int GetArrayFmla()
        {
            if(MASK_ARRAYFMLA == (bGrbitl & MASK_ARRAYFMLA))
            {
                return 1;
            }
            return 0;
        }
    }
}

namespace xlsbHlp
{
    using System;
    public class KRecordEnum
    {
        private const byte RECORD_MASK = 0x80;
        private const int INVALID_ID = -1;
        protected byte[] m_data;
        protected int m_current;
        public void Init(byte[] _data)
        {
            m_data = _data;
            m_current = 0;
        }

        public bool NextRec(ref byte[] ppDat, ref int nRecID, ref int nSize)
        {
            ppDat = null;
            nSize = 0;
            nRecID = GetRecordID();
            if (nRecID == INVALID_ID)
            {
                return false;
            }
            // 最多4个字节
            const int MAX_CB_BYTE = 4;
            for (int i = 0; i < MAX_CB_BYTE; ++i)
            {
                byte bRt = m_data[m_current++];
                nSize *= 128;
                if (RECORD_MASK != (bRt & RECORD_MASK))
                {
                    nSize += System.Convert.ToInt32(bRt);
                    break;
                }
                bRt = System.Convert.ToByte(bRt & (~RECORD_MASK));
                nSize += System.Convert.ToInt32(bRt);
            }
            // 读取数据
            if (nSize > 0)
            {
                if ((m_current + nSize) > m_data.Length)
                {
                    throw new Exception("NextRec: index is out of bound");
                }
                ppDat = new byte[nSize];
                System.Array.Copy(m_data, m_current, ppDat, 0, nSize);
            }
            m_current += nSize;
            return true;
        }

        protected int GetRecordID()
        {
            if (m_current >= m_data.Length)
            {
                return INVALID_ID;
            }
            int nRet;
            byte bRt = m_data[m_current++];
            if (RECORD_MASK == (bRt & RECORD_MASK))
            {
                bRt = System.Convert.ToByte(bRt & (~RECORD_MASK));
                nRet = System.Convert.ToInt32(bRt);
                bRt = m_data[m_current++];
                nRet += System.Convert.ToInt32(bRt) * 128;
            }
            else
            {
                nRet = System.Convert.ToInt32(bRt);
            }
            return nRet;
        }
    }
}

#endif  // #if ENABLE_AutoItX
#endif // #if ENABLE_SharpZipLib